/*
 * SmartCam master processor library
 * Trace generation.
 */

#ifdef PSOS
namespace std { }

using namespace std;

#include <string>
#include <locale>
#endif

#include <fstream>
#include <sstream>
#include <log.hpp>
#include <memfs.h>

#include "globals.h"
#include "trace.h"

static CLog sclp;

static Mfs tfs;
static Mshell tshell(tfs);

static table_t conditionals; /* sct_conditional_t */

class CStr
{
public:
  CStr()
  {
	str = NULL;
  }
  ~CStr()
  {
	if (str) delete str;
  }

  template <class T>
  CStr(T rhs)
  {
	str = NULL;
    buf << rhs;
  }

  template <class T>
  CStr& operator+(T rhs)
  {
    buf << rhs;
    return *this;
  }

  operator char *()
  {
	//std::basic_string<char, std::char_traits<char>, std::allocator<char> > str;
	if (str) delete str;
	str = new std::string(buf.str());
	return (char*)str->c_str();
  }

private:
  std::ostringstream buf;
  std::string *str;
};

extern "C" void scTraInit(void)
{
  sclp.notice() << HDR << "Initializing trace generation" << std::endl;

  abortOnError(STREAM(scl.err), tblcreate(&conditionals, sizeof(sct_conditional_t)));

  scTraUpdateCondition(-1, 0, 0, 0);
}

extern "C" int  scTraGetCondition(int line)
{
  sct_conditional_t *conditional;

  if (!line)
    return 0;

  abortOnNULL(STREAM(scl.err), tblget(&conditionals, line, &conditional));
 
  return conditional->condition; 
}

typedef struct sct_condition_s
{
  int count;
} sct_condition_t;

extern "C" void scTraUpdateCondition(int type, int line, int parent, int condition)
{
  sct_condition_t *c;

  /* Conditions can have multiple parents if in functions */
  sct_conditional_t *conditional;
  
  if (!tblget(&conditionals, line, &conditional))
  {
    abortOnNegative(STREAM(scl.err), tbladdnewi(&conditionals, &conditional, line));
    abortOnError(STREAM(scl.err), tblcreate(&conditional->conditions, sizeof(sct_condition_t)));
  }

  if (type != SWITCH)
    condition = (condition>0);
    
  conditional->type = type;
  conditional->line = line;
  conditional->parent = parent;
  conditional->condition = condition;
  conditional->parent_condition = scTraGetCondition(parent);

  /* Keep count per condition */
  if (!tblget(&conditional->conditions, condition, &c))
    abortOnNegative(STREAM(scl.err), tbladdnewi(&conditional->conditions, &c, condition));

  c->count++;
  conditional->count++;
}

typedef struct sct_peer_s
{
  int count;
} sct_peer_t;

typedef struct sct_arg_s
{
  int type;
  int intent;
  table_t peers;
} sct_arg_t;

void scTraCollapseOperation(int line)
{
  int id=-1, id2=-1, id3=-1;
  sc_op_t *o;
  sc_arg_t *a, *a2;
  sct_peer_t *p;
  sct_arg_t *ta;

  int count=0;
  int type;
  char *name;
  table_t args;  
  
  tblcreate(&args, sizeof(sct_arg_t));

  /* Find all ops on this line */
  while (tblgetnextelement(&sc_ops, &id, &o))
  {
    if (o->line == line)
    {
      type = o->type;
      name = o->text;
      count++;

      /* Find arguments */
      while (tblgetnextelement(&o->arguments, &id2, &a))
      {
	if (!tblget(&args, id2, &ta))
	{
	  abortOnNegative(STREAM(scl.err), tbladdnewi(&args, &ta, id2));
	  abortOnError(STREAM(scl.err), tblcreate(&ta->peers, sizeof(sct_peer_t)));
	}

	ta->type = a->instance->variable->type;
	ta->intent = a->intent;

	if (ta->type == SC_VAR_STREAM)
	{
	  if (ta->intent == SC_INTENT_IN)
	  {
	    /* Find producer */
	    if (!tblget(&ta->peers, a->instance->variable->producer->operation->line, &p))
	      abortOnNegative(STREAM(scl.err), tbladdnewi(&ta->peers, &p, a->instance->variable->producer->operation->line));
	    
	    p->count++;
	  }
	  else
	  {
	    /* Find consumers */
	    /* *** WARNING: Incorrect if a stream goes to one operation twice *** */
	    while (tblgetnextelement(&a->instance->variable->consumers, &id3, &a2))
	    {
	      if (!tblget(&ta->peers, a2->operation->line, &p))
	      abortOnNegative(STREAM(scl.err), tbladdnewi(&ta->peers, &p, a2->operation->line));
	      
	      p->count++;
	    }
	  }
	}
      }
    }
  }

  /* Write collapsed data */

  tshell.MkDir(CStr(line)); tshell.ChDir(CStr(line));
  tshell.MkEntry("type", 1);
  tshell.MkEntry("line", line);
  tshell.MkEntry("optype", type);
  tshell.MkEntry("opname", name);
  tshell.MkEntry("count", count);

  tshell.MkDir("arguments"); tshell.ChDir("arguments");

  while (tblgetnextelement(&args, &id, &ta))
  {
    tshell.MkDir(CStr(id)); tshell.ChDir(CStr(id));

    tshell.MkEntry("type", ta->type);
    tshell.MkEntry("intent", ta->intent);

    tshell.MkDir("peers"); tshell.ChDir("peers");

    while (tblgetnextelement(&ta->peers, &id2, &p))
    {
      tshell.MkDir(CStr(id2)); tshell.ChDir(CStr(id2));

      tshell.MkEntry("line", id2);
      tshell.MkEntry("count", p->count);

      tshell.ChDir("..");
    }

    tshell.ChDir("..");
    tshell.ChDir("..");
  }
  
  tshell.ChDir("..");
  tshell.ChDir("..");
}

void scTraCollapseConditional(table_t *program, int line)
{
  int id=-1, id2=-1;
  sc_op_t *o;
  sct_conditional_t *c, *c2;
  sct_condition_t *ec;
  sct_line_t *l;

  tshell.MkDir(CStr(line)); tshell.ChDir(CStr(line));

  tshell.MkEntry("type", 0);
  tshell.MkEntry("line", line);

  abortOnNULL(STREAM(scl.err), tblget(&conditionals, line, &c));

  tshell.MkEntry("ctype", c->type);
  tshell.MkEntry("count", c->count);

  tshell.MkDir("conditions"); tshell.ChDir("conditions");

  /* Loop over encountered conditions */
  while (tblgetnextelement(&c->conditions, &id, &ec))
  {
    tshell.MkDir(CStr(id)); tshell.ChDir(CStr(id));
    tshell.MkEntry("value", id);
    tshell.MkEntry("count", ec->count);

    tshell.MkDir("children"); tshell.ChDir("children");

    while (tblgetnextelement(program, &id2, &l))
    {
      if (l->parent == line)
      {
	if (l->type == 0)
	{
	  abortOnNULL(STREAM(scl.err), tblget(&conditionals, l->line, &c2));

	  if (c2->parent_condition == id)
	    scTraCollapseConditional(program, l->line);
	}
	else
	{
	  /* *** DOES NOT WORK WITH CASE FALLTHROUGHS *** */
	  abortOnNULL(STREAM(scl.err),tblget(&sc_ops, l->id, &o));

	  if (o->parent_condition == id)	 
	    scTraCollapseOperation(l->line);
	}
      }
    }

    tshell.ChDir("..");
    tshell.ChDir("..");
  }

  tshell.ChDir("..");
  tshell.ChDir("..");
}


extern "C" void scTraCollapse(void)
{
  int id=-1;
  sc_op_t *o;
  sct_conditional_t *c;
  sct_line_t *l;
  table_t program;

  /* Create a "program table" */
  abortOnError(STREAM(scl.err), tblcreate(&program, sizeof(sct_line_t)));

  /* Fill */
  while (tblgetnextelement(&conditionals, &id, &c))
    if (c->line && !tblget(&program, c->line, &l))
    {
      abortOnNegative(STREAM(scl.err), tbladdnewi(&program, &l, c->line));
      l->line = c->line;
      l->type = 0;
      l->parent = c->parent;
    }

  while (tblgetnextelement(&sc_ops, &id, &o))
  {
    if (!tblget(&program, o->line, &l))
    {
      abortOnNegative(STREAM(scl.err), tbladdnewi(&program, &l, o->line));

      l->line = o->line;
      l->type = 1;
      l->parent = o->parent;
      l->id = o->id;
    }
  }

  abortOnError(STREAM(scl.err), tblsortslice(&program, SCU_TABLE_SLICE_OCCUPIED));

  tshell.MkDir("conditionals"); tshell.ChDir("conditionals");

  /* Recurse */
  scTraCollapseConditional(&program, 0);
  
  tshell.ChDir("..");
}

extern "C" void scTraWrite(void)
{
#ifndef PSOS
  int id=-1, id2=-1;
  sc_proc_t *p;
  sc_buf_t *b;
  sc_var_t *v;
  sc_op_t *o;
  sc_arg_t *a;
  sc_originref_t *r;

  if (!settings.tracefile) return;
  
  sclp.info() << HDR << "Writing trace to " << settings.tracefile << std::endl;

  tshell.RmDir("/");
  tshell.MkDir("trace"); tshell.ChDir("trace");
  tshell.MkDir("processors"); tshell.ChDir("processors");

  sclp.debug() << HDR << "Processors" << std::endl;

  while (tblget(&sc_procs, id = tblgetnexti(&sc_procs, id), &p))
  {
    tshell.MkDir(CStr(p->id)); tshell.ChDir(CStr(p->id));

    tshell.MkEntry("id", p->id);
    tshell.MkEntry("name", p->name);

    tshell.MkDir("buffers"); tshell.ChDir("buffers");
    
    while ((id2 = tblgetnexti(&p->buffers, id2)) >= 0)
    {
      abortOnNegative(STREAM(sclp.err.GetStream()), tblget(&p->buffers, id2, &b));
      tshell.MkEntry(CStr(b->id), "");
    }

    tshell.ChDir("..");
    
    tshell.MkDir("operations"); tshell.ChDir("operations");
    while ((id2 = tblgetnexti(&p->operations, id2)) >= 0)
    {
      abortOnNegative(STREAM(sclp.err.GetStream()), tblget(&p->operations, id2, &o));
      tshell.MkEntry(CStr(o->id), "");
    }

    tshell.ChDir("..");
    tshell.ChDir("..");
  }
  
  tshell.ChDir("..");
  tshell.MkDir("streams"); tshell.ChDir("streams");

  sclp.debug() << HDR << "Streams" << std::endl;

  while ((id = tblgetnexti(&sc_vars, id)) >= 0)
  {
    abortOnNegative(STREAM(sclp.err.GetStream()), tblget(&sc_vars, id, &v));

    if (v->type == SC_VAR_STREAM)
    {
      tshell.MkDir(CStr(v->id)); tshell.ChDir(CStr(v->id));

      tshell.MkEntry("id", v->id);
      tshell.MkEntry("bytes", v->flowsize);

      tshell.MkDir("buffers"); tshell.ChDir("buffers");

      while ((id2 = tblgetnexti(&v->buffers, id2)) >= 0)
      {
	abortOnNegative(STREAM(sclp.err.GetStream()), tblget(&v->buffers, id2, &b));
	tshell.MkEntry(CStr(b->id), "");
      }
      
      tshell.ChDir("..");

      tshell.MkDir("origins"); tshell.ChDir("origins");

      while ((id2 = tblgetnexti(&v->originrefs, id2)) >= 0)
      {
	abortOnNegative(STREAM(sclp.err.GetStream()), tblget(&v->originrefs, id2, &r));

	tshell.MkDir(CStr(r->origin->id)); tshell.ChDir(CStr(r->origin->id));

	tshell.MkEntry("id", r->origin->id);
	tshell.MkEntry("delay", r->delay);

	tshell.ChDir("..");
      }
      
      tshell.ChDir("..");

      tshell.ChDir("..");
    }
  }

  tshell.ChDir("..");
  tshell.MkDir("buffers"); tshell.ChDir("buffers");

  sclp.debug() << HDR << "Buffers" << std::endl;

  while ((id = tblgetnexti(&sc_bufs, id)) >= 0)
  {
    abortOnNegative(STREAM(sclp.err.GetStream()), tblget(&sc_bufs, id, &b));

    tshell.MkDir(CStr(b->id)); tshell.ChDir(CStr(b->id));

    tshell.MkEntry("id", b->id);
    tshell.MkEntry("file", b->file);
    tshell.MkEntry("size", b->size);
    tshell.MkEntry("processor", b->processor->id);
    tshell.MkEntry("stream", b->stream->id);

    tshell.ChDir("..");
  }

  tshell.ChDir("..");  
  tshell.MkDir("operations"); tshell.ChDir("operations");

  sclp.debug() << HDR << "Operations" << std::endl;

  while ((id = tblgetnexti(&sc_ops, id)) >= 0)
  {
    abortOnNegative(STREAM(sclp.err.GetStream()), tblget(&sc_ops, id, &o));

    tshell.MkDir(CStr(o->id)); tshell.ChDir(CStr(o->id));

    tshell.MkEntry("id", o->id);
    tshell.MkEntry("type", o->type);
    tshell.MkEntry("line", o->line);
    tshell.MkEntry("parent", o->parent);
    tshell.MkEntry("parent_condition", o->parent_condition);
    tshell.MkEntry("text", o->text);
    tshell.MkEntry("processor", o->processor->id);
    tshell.MkEntry("time", 0);

    tshell.MkDir("arguments"); tshell.ChDir("arguments");

    while ((id2 = tblgetnexti(&o->arguments, id2)) >= 0)
    {
      abortOnNegative(STREAM(sclp.err.GetStream()), tblget(&o->arguments, id2, &a));
      
      tshell.MkDir(CStr(a->id)); tshell.ChDir(CStr(a->id));
      
      tshell.MkEntry("id", a->id);
      tshell.MkEntry("type", a->instance->variable->type);
      tshell.MkEntry("intent", a->intent);

      switch (a->instance->variable->type)
      {
	case SC_VAR_STREAM:
	  tblget(&a->instance->variable->buffers, a->operation->processor->id, &b);
	  tshell.MkEntry("i", b->id);
	  break;
	case SC_VAR_INTEGER:
	  tshell.MkEntry("d", *((int*)a->instance->variable->data));
	  break;
	case SC_VAR_DOUBLE:
	  tshell.MkEntry("f", *((double*)a->instance->variable->data));
	  break;
      }

      tshell.ChDir("..");
    }

    tshell.ChDir("..");

    tshell.ChDir("..");
  }
  
  tshell.ChDir("..");

  sclp.debug() << HDR << "Conditionals" << std::endl;

  scTraCollapse();

  std::ofstream tof(settings.tracefile);
  abortOnError(STREAM(sclp.err.GetStream()), !tof.is_open());
  XMLParser txp(tof);
  tshell.Export(txp, "/");  
#endif
}
